package linc.fun.openai.strategy.user;

import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import jakarta.annotation.Resource;
import linc.fun.openai.constants.ApplicationConstants;
import linc.fun.openai.domain.dto.request.RegisterFrontUserForEmailRequest;
import linc.fun.openai.domain.entity.sys.SysEmailVerifyCodeDO;
import linc.fun.openai.domain.entity.chat.ChatUserDO;
import linc.fun.openai.domain.entity.chat.ChatUserBindingDO;
import linc.fun.openai.domain.entity.chat.ChatUserBindingEmailDO;
import linc.fun.openai.domain.vo.LoginInfoVO;
import linc.fun.openai.domain.vo.UserInfoVO;
import linc.fun.openai.enums.SysEmailBizTypeEnum;
import linc.fun.openai.enums.ChatUserRegisterTypeEnum;
import linc.fun.openai.exception.BizException;
import linc.fun.openai.service.*;
import linc.fun.openai.util.IdGenerator;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.Objects;

import static linc.fun.openai.constants.ApplicationConstants.FRONT_JWT_EXTRA_USER_ID;
import static linc.fun.openai.constants.ApplicationConstants.FRONT_JWT_USERNAME;

/**
 * 邮箱注册策略
 *
 * @author CoDeleven
 */
@Lazy
@Component("EmailRegisterStrategy")
public class EmailAbstractRegisterStrategy extends AbstractRegisterTypeStrategy {

    @Resource
    private ChatUserBindingEmailService chatUserBindingEmailService;

    @Resource
    private ChatUserService chatUserService;

    @Resource
    private SysEmailVerifyCodeService emailVerifyCodeService;

    @Resource
    private ChatUserBindingService chatUserBindingService;

    @Resource
    private SysEmailService emailService;

    @Resource
    private ChatUserLoginLogService loginLogService;
    @Resource
    private PasswordEncoder passwordEncoder;
    @Resource
    private IdGenerator idGenerator;

    @Override
    public boolean identityUsed(String identity) {
        return chatUserBindingEmailService.isUsed(identity);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void checkVerifyCode(String identity, String verifyCode) {
        // 校验邮箱验证码
        SysEmailVerifyCodeDO availableVerifyCode = emailVerifyCodeService.findAvailableByVerifyCode(verifyCode);
        if (Objects.isNull(availableVerifyCode)) {
            throw BizException.CODE_NOT_EXIST_OR_EXPIRED;
        }
        // 验证通过，生成基础用户信息并做绑定
        ChatUserDO baseUser = chatUserService.createEmptyUser();
        // 获取邮箱信息表
        ChatUserBindingEmailDO userBindingEmail = chatUserBindingEmailService.getUnverifiedEmailAccount(availableVerifyCode.getToEmailAddress());
        // 绑定两张表
        chatUserBindingService.bindEmail(baseUser, userBindingEmail);
        // 验证完毕，写入日志
        emailVerifyCodeService.verifySuccess(availableVerifyCode);
        // 设置邮箱已验证
        chatUserBindingEmailService.verifySuccess(userBindingEmail);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void register(RegisterFrontUserForEmailRequest request) {
        // 注册前会校验账号信息，到注册时能确保账号都是可以注册的

        // 查找邮箱账号是否存在
        ChatUserBindingEmailDO existsEmailDO = chatUserBindingEmailService.getUnverifiedEmailAccount(request.getIdentity());

        // 加密密码
        String encodePassword = passwordEncoder.encode(request.getPassword());

        // 构建新的邮箱信息
        if (Objects.isNull(existsEmailDO)) {
            existsEmailDO = ChatUserBindingEmailDO
                    .builder()
                    .id(idGenerator.getId())
                    .password(encodePassword)
                    .username(request.getIdentity())
                    .verified(false)
                    .build();
            // 存储邮箱信息
            chatUserBindingEmailService.save(existsEmailDO);

        } else {

            // 在未使用的邮箱基础上更新下密码信息，然后重新投入使用
            existsEmailDO.setVerified(false);
            existsEmailDO.setPassword(encodePassword);
            // 存储邮箱信息
            chatUserBindingEmailService.updateById(existsEmailDO);

        }

        // 存储验证码记录
        SysEmailVerifyCodeDO emailVerifyCodeDO = emailVerifyCodeService.createVerifyCode(SysEmailBizTypeEnum.REGISTER_VERIFY, request.getIdentity());

        // TODO 根据 ip 进行限流

        // 发送邮箱验证信息
        emailService.sendForVerifyCode(request.getIdentity(), emailVerifyCodeDO.getVerifyCode());
    }

    @Override
    public UserInfoVO getLoginUserInfo(Long userBindingEmailId) {

        ChatUserBindingEmailDO userBindingEmail = chatUserBindingEmailService.getById(userBindingEmailId);

        // 根据注册类型+userBindingEmailId获取 当前邮箱绑定在了哪个用户上
        ChatUserBindingDO userBinding = chatUserBindingService.findBindingEmail(ChatUserRegisterTypeEnum.EMAIL, userBindingEmailId);

        if (Objects.isNull(userBinding)) {
            throw BizException.of(StrUtil.format("注册方式：{} 额外信息ID：{} 绑定关系不存在",
                    ChatUserRegisterTypeEnum.EMAIL.getDesc(), userBindingEmailId));
        }

        // 根据绑定关系查找基础用户信息
        ChatUserDO user = chatUserService.findUserInfoById(userBinding.getUserId());
        if (Objects.isNull(user)) {
            throw BizException.of(StrUtil.format("基础用户不存在：{}", userBinding.getUserId()));
        }

        // 封装基础用户信息并返回
        return UserInfoVO.builder().userId(user.getId())
                .description(user.getDescription())
                .nickname(user.getNickname())
                .email(userBindingEmail.getUsername())
                .avatarUrl("")
                .canConversationTimes(user.getCanConversationTimes())
                .build();
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public LoginInfoVO login(String username, String password) {

        // 验证账号信息
        ChatUserBindingEmailDO emailDO = chatUserBindingEmailService.getEmailAccount(username);
        if (Objects.isNull(emailDO) || BooleanUtil.isFalse(emailDO.getVerified())) {
            throw BizException.EMAIL_NOT_REGISTER;
        }

        // 二次加密，验证账号密码
        if (!passwordEncoder.matches(password, emailDO.getPassword())) {

            Long userId = 0L;

            // 获取绑定的基础用户 id
            ChatUserBindingDO userExtraBindingDO = chatUserBindingService.findBindingEmail(ChatUserRegisterTypeEnum.EMAIL, emailDO.getId());
            if (Objects.nonNull(userExtraBindingDO)) {
                ChatUserDO userBaseDO = chatUserService.findUserInfoById(userExtraBindingDO.getUserId());
                if (Objects.nonNull(userBaseDO)) {
                    userId = userBaseDO.getId();
                }
            }
            // TODO: 异步日志处理
            // 记录登录失败日志
            loginLogService.loginFailed(ChatUserRegisterTypeEnum.EMAIL, emailDO.getId(), userId, "账号或密码错误");

            throw BizException.USERNAME_PASSWORD_ERROR;
        }

        // 获取登录用户信息
        UserInfoVO userInfo = this.getLoginUserInfo(emailDO.getId());

        // 执行登录
        StpUtil.login(userInfo.getUserId(), SaLoginModel.create()
                .setExtra(FRONT_JWT_USERNAME, emailDO.getUsername())
                .setExtra(ApplicationConstants.FRONT_JWT_REGISTER_TYPE_CODE, ChatUserRegisterTypeEnum.EMAIL.getCode())
                .setExtra(FRONT_JWT_EXTRA_USER_ID, emailDO.getId()));

        // 记录登录日志
        loginLogService.loginSuccess(ChatUserRegisterTypeEnum.EMAIL, emailDO.getId(), userInfo.getUserId());

        return LoginInfoVO.builder().token(StpUtil.getTokenValue()).userId(userInfo.getUserId()).build();
    }
}
